package com.societyGames.codeAlong.codeExercise
{
import com.codeazur.as3swf.SWF;
import com.codeazur.as3swf.SWFData;
import com.codeazur.as3swf.tags.TagDoABC;
import com.codeazur.as3swf.tags.TagEnd;
import com.codeazur.as3swf.tags.TagFileAttributes;
import com.codeazur.as3swf.tags.TagShowFrame;

import flash.display.Loader;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.ProgressEvent;
import flash.system.ApplicationDomain;
import flash.system.LoaderContext;
import flash.utils.ByteArray;
import flash.utils.clearInterval;
import flash.utils.setInterval;

/**
 * So you don't want to back-port changes in ESC to AS3 every other day?
 * And yet you want to reap the benefits of ESC's updates?
 *
 * Ok. Go with binary.
 *
 */
public class CompiledESC extends EventDispatcher
{
  public static const instance:CompiledESC = new CompiledESC();
  public static const INITIALIZED:String = "INITIALIZED";
  private static const STATE_NONE:int = 0;
  private static const STATE_LOADING:int = 1;
  private static const STATE_LOADED:int = 2;
  private static var _state:int = STATE_NONE;
  private static var _loader:Loader;
  private static var _filterCommandLineFunction:Function;
  private static var _compileStringToBytesFunction:Function;

  private var _intervalID:int;

  public function initialize():void
  {
    if (_state == STATE_NONE)
    {
      _state = STATE_LOADING;
      loadESC();
    }
    else
    {
      if (_state == STATE_LOADED)
      {
        this.dispatchEvent(new Event(INITIALIZED));
      }
    }
  }

  [Embed(source="../../../../../abc/debug.es.abc", mimeType="application/octet-stream")]
  private var debug_abc:Class;
  [Embed(source="../../../../../abc/util.es.abc", mimeType="application/octet-stream")]
  private var util_abc:Class;
  [Embed(source="../../../../../abc/bytes-tamarin.es.abc", mimeType="application/octet-stream")]
  private var bytes_tamarin_abc:Class;
  [Embed(source="../../../../../abc/util-tamarin.es.abc", mimeType="application/octet-stream")]
  private var util_tamarin_abc:Class;
  [Embed(source="../../../../../abc/lex-char.es.abc", mimeType="application/octet-stream")]
  private var lex_char_abc:Class;
  [Embed(source="../../../../../abc/lex-scan.es.abc", mimeType="application/octet-stream")]
  private var lex_scan_abc:Class;
  [Embed(source="../../../../../abc/lex-token.es.abc", mimeType="application/octet-stream")]
  private var lex_token_abc:Class;
  [Embed(source="../../../../../abc/ast.es.abc", mimeType="application/octet-stream")]
  private var ast_abc:Class;
  [Embed(source="../../../../../abc/parse.es.abc", mimeType="application/octet-stream")]
  private var parse_abc:Class;
  [Embed(source="../../../../../abc/asm.es.abc", mimeType="application/octet-stream")]
  private var asm_abc:Class;
  [Embed(source="../../../../../abc/abc.es.abc", mimeType="application/octet-stream")]
  private var abc_abc:Class;
  [Embed(source="../../../../../abc/emit.es.abc", mimeType="application/octet-stream")]
  private var emit_abc:Class;
  [Embed(source="../../../../../abc/cogen.es.abc", mimeType="application/octet-stream")]
  private var cogen_abc:Class;
  [Embed(source="../../../../../abc/cogen-stmt.es.abc", mimeType="application/octet-stream")]
  private var cogen_stmt_abc:Class;
  [Embed(source="../../../../../abc/cogen-expr.es.abc", mimeType="application/octet-stream")]
  private var cogen_expr_abc:Class;
  [Embed(source="../../../../../abc/esc-core.es.abc", mimeType="application/octet-stream")]
  private var esc_core_abc:Class;
  [Embed(source="../../../../../abc/eval-support.es.abc", mimeType="application/octet-stream")]
  private var eval_support_abc:Class;
  [Embed(source="../../../../../abc/esc-env.es.abc", mimeType="application/octet-stream")]
  private var esc_env_abc:Class;
  [Embed(source="../../../../../abc/define.es.abc", mimeType="application/octet-stream")]
  private var define_abc:Class;
  [Embed(source="../../../../../abc/main.es.abc", mimeType="application/octet-stream")]
  private var main_abc:Class;

  private function loadESC():void
  {
    var escSWF:SWF = new SWF();
    escSWF.version = 9;
    escSWF.frameSize.xmax = 0;
    escSWF.frameSize.ymax = 0;
    escSWF.frameRate = 10;
    escSWF.frameCount = 2;
    escSWF.compressed = false;
    escSWF.tags.push(new TagFileAttributes());

    var abcByteArrays:Array = [
      new debug_abc as ByteArray,
      new util_abc as ByteArray,
      new bytes_tamarin_abc as ByteArray,
      new util_tamarin_abc as ByteArray,
      new lex_char_abc as ByteArray,
      new lex_scan_abc as ByteArray,
      new lex_token_abc as ByteArray,
      new ast_abc as ByteArray,
      new parse_abc as ByteArray,
      new asm_abc as ByteArray,
      new abc_abc as ByteArray,
      new emit_abc as ByteArray,
      new cogen_abc as ByteArray,
      new cogen_stmt_abc as ByteArray,
      new cogen_expr_abc as ByteArray,
      new esc_core_abc as ByteArray,
      new eval_support_abc as ByteArray,
      new esc_env_abc as ByteArray,
      new define_abc as ByteArray, //Needed?
      new main_abc as ByteArray, //Needed?
    ];

    for each(var abcByteArray:ByteArray in abcByteArrays)
    {
      escSWF.tags.push(TagDoABC.create(abcByteArray));
    }

    escSWF.tags.push(new TagShowFrame());
    escSWF.tags.push(new TagShowFrame());
    escSWF.tags.push(new TagEnd());

    var swfData:SWFData = new SWFData();
    escSWF.publish(swfData);
    escSWF = null;

    swfData.position = 0;

    var loaderContext:LoaderContext = new LoaderContext(false, ApplicationDomain.currentDomain);
    loaderContext.allowCodeImport = true; //Security hole, but needed for running compiler in AIR.

    _loader = new Loader();
    _loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loaderCompleteHandler);
    _loader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, loaderProgressHandler);
    _loader.loadBytes(swfData, loaderContext);
  }

  private function loaderCompleteHandler(event:Event):void
  {
    _state = STATE_LOADED;
    _loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, loaderCompleteHandler);
    _filterCommandLineFunction = ApplicationDomain.currentDomain.getDefinition('ESC.filterCommandLine') as Function;
    _compileStringToBytesFunction = ApplicationDomain.currentDomain.getDefinition('ESC.compileStringToBytes') as Function;
    this.dispatchEvent(new Event(INITIALIZED));
  }

  private function loaderProgressHandler(event:ProgressEvent):void
  {
    if (event.bytesLoaded == event.bytesTotal)
    {
      _intervalID = setInterval(intervalHandler, 1);
    }
  }

  private function intervalHandler():void
  {
    clearInterval(_intervalID);
  }

  public function eval(code:String, identifier:String):ByteArray
  {
    _filterCommandLineFunction(["-debug"]);
    var abcByteArray:ByteArray = _compileStringToBytesFunction(code, identifier);
    abcByteArray.position = 0;
    return abcByteArray;
  }
}
}